package org.dicr.sys.linux;

import java.io.*;
import java.lang.ref.*;
import java.util.logging.*;

import org.dicr.util.data.*;

/**
 * Датчик процессора.
 * <P>
 * Информация о моделе процессора не меняется со временем и не обновляется. Информация о загрузке процессора кэшируется
 * в течении
 * 
 * @{link #cacheTime} времени.
 *        </P>
 * @author Igor A Tarasov, &lt;java@dicr.org&gt;
 * @version 1.0
 * @see CPUInfoData
 * @see CPULoadData
 */
public class CPUInfo {
	private static final Logger log = Logger.getLogger(CPUInfo.class.getName());

	/**
     * Файл информации о процессоре.
     */
	public static final String PROCINFO = "/proc/cpuinfo";

	/**
     * Файл статистики процессора.
     */
	public static final String PROCSTAT = "/proc/stat";

	/**
     * Файл загрузки процессора.
     */
	public static final String PROCLOAD = "/proc/loadavg";

	/**
     * Информация о процессоре.
     */
	protected static SoftReference<CPUInfoData> infoRef = null;

	/**
     * Информаци о загрузке процессора.
     */
	protected static SoftReference<CPULoadData> loadRef = null;

	/**
     * Время кэширования данных.
     */
	protected static long cacheTime = 1000;

	/**
     * Возвращает время кэширования.
     * 
     * @return время кэширования данных в миллисекундах.
     */
	public static synchronized long getCacheTime() {
		return cacheTime;
	}

	/**
     * Устанавливает время кэширования.
     * 
     * @param millisec время кэширования данных о загрузке в миллисекундах.
     */
	public static synchronized void setCacheTime(long millisec) {
		if (millisec < 0) {
			throw new IllegalArgumentException("negative cache period");
		}
		cacheTime = millisec;
		log.config("configured cacheTime=" + millisec);
	}

	/**
     * Вовзращает информацию о процессоре.
     * 
     * @return информация о процессоре.
     * @throws FileNotFoundException не найден системный файл состояния процессора {@value #PROCINFO}
     * @throws IOException ошибка чтения файла состояния
     * @throws NumberFormatException неизвестный формат файла соятония процессора
     * @see CPUInfoData
     */
	public static synchronized CPUInfoData getInfo() throws IOException, FileNotFoundException, NumberFormatException {
		CPUInfoData info = null;
		if (infoRef != null) {
			info = infoRef.get();
		}
		if (info != null) {
			return info;
		}
		BufferedReader in = null;
		try {
			String name = null;
			String vendor = null;
			int family = 0;
			int model = 0;
			int stepping = 0;
			long freq = 0;
			int cacheSize = 0;
			long bogomips = 0;
			in = new BufferedReader(new FileReader(new File(PROCINFO)));
			while (true) {
				String line = in.readLine();
				if (line == null) {
					break;
				}
				line = StringUtils.normalize(line);
				if (line == null) {
					continue;
				}
				int pos = line.indexOf(':');
				if (pos < 0) {
					log.warning("error parsing file '" + PROCINFO + "' line '" + line + "'");
					continue;
				}
				String key = line.substring(0, pos).trim().toLowerCase();
				String val = line.substring(pos + 1).trim();
				if (key.equals("vendor_id")) {
					vendor = val;
				} else if (key.equals("cpu family")) {
					family = Integer.parseInt(val);
				} else if (key.equals("model")) {
					model = Integer.parseInt(val);
				} else if (key.equals("model name")) {
					name = val;
				} else if (key.equals("stepping")) {
					stepping = Integer.parseInt(val);
				} else if (key.equals("cpu mhz")) {
					freq = Math.round(Float.parseFloat(val) * 1000);
				} else if (key.equals("cache size")) {
					cacheSize = Integer.parseInt(val.split("\\s+")[0]) * 1024;
				} else if (key.equals("bogomips")) {
					bogomips = Math.round(Double.parseDouble(val));
				}
			}
			in.close();
			in = null;
			info = new CPUInfoData(name, vendor, family, model, stepping, freq, bogomips, cacheSize);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception ex) {
					// NOOP
				}
			}
		}
		return info;
	}

	/**
     * Возвращает информацию о загрузке процессора.
     * 
     * @return информация о загрузке процессора.
     * @throws FileNotFoundException ошибка открытия файла состояния процессора {@value #PROCSTAT} или
     *             {@value #PROCLOAD}
     * @throws IOException ошибка чтения файла состояния процессора.
     * @throws NumberFormatException неизвестный формат файла соятония процессора
     * @see CPULoadData
     */
	public static synchronized CPULoadData getLoad() throws IOException, FileNotFoundException, NumberFormatException {
		CPULoadData load = null;
		if (loadRef != null) {
			load = loadRef.get();
		}
		if (load != null && System.currentTimeMillis() - load.getTimeStamp() < cacheTime) {
			return load;
		}
		BufferedReader in = null;
		try {
			long timesSystem = 0;
			long timesUser = 0;
			long timesIdle = 0;
			int numProcesses = 0;
			float[] loadAvg = new float[3];
			// read proc stat
			in = new BufferedReader(new FileReader(PROCSTAT));
			String line = in.readLine();
			in.close();
			in = null;
			if (line == null) {
				throw new NumberFormatException("Неизвестный формат файла " + PROCSTAT);
			}
			String vals[] = line.split("\\s+");
			if (vals.length < 5) {
				throw new NumberFormatException("Неизвестный формат файла " + PROCSTAT);
			}
			timesUser = Long.parseLong(vals[1]);
			timesSystem = Long.parseLong(vals[2]) + Long.parseLong(vals[3]);
			timesIdle = Long.parseLong(vals[4]);
			// read load average
			in = new BufferedReader(new FileReader(PROCLOAD));
			line = in.readLine();
			in.close();
			in = null;
			if (line == null) {
				throw new NumberFormatException("Неизвестный формат файла " + PROCLOAD);
			}
			vals = line.split("[\\s\\/]+");
			if (vals.length < 5) {
				throw new NumberFormatException("Неизвестный формат файла " + PROCLOAD);
			}
			loadAvg[0] = Float.parseFloat(vals[0]);
			loadAvg[1] = Float.parseFloat(vals[1]);
			loadAvg[2] = Float.parseFloat(vals[2]);
			numProcesses = Integer.parseInt(vals[4]);
			load = new CPULoadData(numProcesses, loadAvg, timesSystem, timesUser, timesIdle);
		} finally {
			try {
				if (in != null) in.close();
			} catch (Exception ex) {
				// NOOP
			}
		}
		return load;
	}
}
